package com.dbflow5.query.list;

import com.dbflow5.database.DatabaseWrapper;
import com.dbflow5.database.FlowCursor;
import com.dbflow5.query.ModelQueriable;
import com.dbflow5.query.Transformable;
import com.dbflow5.sql.QueryCloneable;
import java.io.Closeable;
import java.util.ListIterator;

/**
 * Description: Provides iteration capabilities to a [FlowCursorList].
 */
public class FlowCursorIterator<T> implements ListIterator<T>, AutoCloseable, Closeable {
    private long reverseIndex;
    private long startingCount;
    private final IFlowCursorIterator<T> cursorList;

    private long count = 0;

    public FlowCursorIterator(DatabaseWrapper databaseWrapper, IFlowCursorIterator<T> cursorList, long startingLocation, long count) {
        if(count == 0){
            count = cursorList.count() - startingLocation;
        }

        IFlowCursorIterator<T> newCursorList = cursorList;
        long newStartingLocation = startingLocation;
        if (!cursorList.trackingCursor()) {
            // no cursor specified, we can optimize the query to return results within SQL range, rather than all rows.
            FlowCursorList<T> _cursorList = null;
            if(cursorList instanceof FlowCursorList<?>){
                _cursorList = (FlowCursorList<T>) cursorList;
            }else if (cursorList instanceof FlowQueryList<?>) {
                _cursorList = (FlowCursorList<T>)(((FlowQueryList<?>)cursorList).internalCursorList);
            }else {
                throw new IllegalArgumentException("The specified "+IFlowCursorIterator.class.getSimpleName()+" " +
                        "must track cursor unless it is a FlowCursorList or FlowQueryList");
            }

            ModelQueriable<T> modelQueriable = _cursorList.modelQueriable;
            if (modelQueriable instanceof Transformable<?>) {
                newCursorList = ((Transformable<T>)modelQueriable).constrain(startingLocation, count).cursorList(databaseWrapper);
                this.count = newCursorList.count();
                newStartingLocation = 0;
            }
        }
        FlowCursor cursor = newCursorList.cursor();
        // request larger than actual count. Can almost never be long, but we keep precision.
        if (this.count > cursor.getRowCount() - newStartingLocation) {
            this.count = cursor.getRowCount() - newStartingLocation;
        }

        cursor.goToRow((int)newStartingLocation - 1);
        startingCount = newCursorList.cursor().getRowCount();
        reverseIndex = this.count;
        reverseIndex -= newStartingLocation;

        if (reverseIndex < 0) {
            reverseIndex = 0;
        }

        this.cursorList = newCursorList;
    }

    public FlowCursorIterator(DatabaseWrapper databaseWrapper, IFlowCursorIterator<T> cursorList) {
        this(databaseWrapper, cursorList, 0, cursorList.count());
    }

    public boolean isClosed() {
        return cursorList.isClosed();
    }

    @Override
    public void close() {
        cursorList.close();
    }

    @Override
    public boolean hasNext() {
        checkSizes();
        return reverseIndex > 0;
    }

    @Override
    public boolean hasPrevious() {
        checkSizes();
        return reverseIndex < count;
    }

    @Override
    public T next() {
        checkSizes();
        T item = cursorList.get(count - reverseIndex);
        reverseIndex--;
        return item;
    }

    @Override
    public int nextIndex() {
        return (int)(reverseIndex + 1);
    }

    @Override
    public T previous() {
        checkSizes();
        T item = cursorList.get(count - reverseIndex);
        reverseIndex++;
        return item;
    }

    @Override
    public int previousIndex() {
        return (int)reverseIndex;
    }

    @Override
    public void remove() {
    }

    @Override
    public void set(T t) {
    }

    @Override
    public void add(T t) {
    }

    private void checkSizes() {
        if (startingCount != cursorList.count()) {
            throw new RuntimeException("Concurrent Modification: Cannot change Cursor data " +
                    "during iteration. Expected "+startingCount+", found: "+cursorList.count());
        }
    }

    private ModelQueriable<T> getQueriableFromParams(Transformable<T> transformable, long startPosition, long max) {
        Transformable<T> tr = transformable;
        if (tr instanceof QueryCloneable<?>) {
            tr = (Transformable<T>) (((QueryCloneable<?>)tr).cloneSelf());
        }
        return tr.offset(startPosition).limit(max);
    }
}
